package ee.logsimulator;

import ee.logsimulator.data.Actor;
import ee.logsimulator.data.Clazz;
import ee.logsimulator.data.Event;
import ee.logsimulator.data.Mode;
import ee.logsimulator.parser.LogParser;

import java.awt.Color;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.htmlparser.util.ParserException;

/**
 * @author Romet Piho
 * 
 */
public class Model {

  private static final Color[] TARGET_COLORS = new Color[] { Color.WHITE, Color.CYAN, Color.YELLOW, Color.GREEN, Color.ORANGE, Color.PINK,
      Color.RED, Color.BLUE, Color.MAGENTA };
  private List<Event> events = new ArrayList<Event>();
  private Event first;
  private Event last;
  private Set<Actor> sourceActors = new HashSet<Actor>();
  private Set<Actor> sourceFilteredActors = new HashSet<Actor>();
  private Set<Actor> targetActors = new HashSet<Actor>();
  private Set<Actor> targetFilteredActors = new HashSet<Actor>();
  private Map<Actor, Color> targetColors = new HashMap<Actor, Color>();
  private Map<Actor, List<Event>> playerEvents = new HashMap<Actor, List<Event>>();
  private Mode mode = Mode.DAMAGE;
  private int nextColor = 0;

  public void addLogText(String url) throws ParseException, IOException, ParserException {
    StringBuilder sb = new StringBuilder(url);
    if (url.contains("&x=")) {
      sb.append("+and+");
    } else {
      sb.append("&x=");
    }
    switch (mode) {
    case RESTO_DRUID:
      sb.append("type+%3D+TYPE_DEATH+or+%28type+%3D+TYPE_DAMAGE+and+targettype+%3D+%22Player%22%29+or%0D%0A%28type+%3D+TYPE_HEAL+and+targettype+%3D+%22Player%22%29+or+spell+in+%28%22Rejuvenation%22%2C+%22Wild+Growth%22%2C+%22Lifebloom%22%2C+%22Regrowth%22%2C+%22Nourish%22%2C+%22Healing+Touch%22%2C+%22Swiftmend%22%2C+%22Tree+of+Life%22%2C+%22Tranquility%22%2C+%22Clearcasting%22%2C+%22Efflorescence%22%2C+%22Nature%27s+Grace%22%2C+%22Innervate%22%2C+%22Barkskin%22%2C+%22Nature%27s+Swiftness%22%2C+%22Harmony%22%2C+%22Rebirth%22%29");
      break;
    case DAMAGE:
      sb.append("type%3DTYPE_DAMAGE");
      break;
    case HEALING:
      sb.append("type%3DTYPE_HEAL");
      break;
    }
    LogParser parser = new LogParser(sb.toString());
    List<Event> addEvents = parser.getParesedLog();
    if (events.isEmpty()) {
      events.addAll(addEvents);
    } else {
      combineEvents(addEvents);
    }
    simulate();
    if (mode == Mode.RESTO_DRUID) {
      sourceFilteredActors.clear();
      simulate(false);
    }
  }

  public void clearLogText() {
    events.clear();
    resetLists();
  }

  private void resetLists() {
    playerEvents.clear();
    sourceActors.clear();
    sourceFilteredActors.clear();
    targetActors.clear();
    targetFilteredActors.clear();
    targetColors.clear();
    first = null;
    last = null;
    nextColor = 0;
  }

  public Color getTartetColor(Actor target) {
    Clazz clazz = Clazz.fromName(target.getActorClass());
    Color color = targetColors.get(target);
    if (color == null) {
      if (clazz.isPlayer() && !targetColors.containsValue(clazz.getColor())) {
        color = clazz.getColor();
      } else {
        color = nextColor();
      }
      targetColors.put(target, color);
    }
    return color;
  }

  public void simulate() {
    simulate(true);
  }

  public void simulate(boolean reset) {
    Collections.sort(events);
    if (reset) {
      resetLists();
    } else {
      first = null;
      last = null;
      playerEvents.clear();
    }
    for (Event e : events) {
      if (reset) {
        sourceActors.add(e.getSource());
        targetActors.add(e.getTarget());
      }
      if (!reset && (!sourceFilteredActors.contains(e.getSource()) || !targetFilteredActors.contains(e.getTarget()))) {
        continue;
      }
      if (first == null || e.compareTo(first) < 0) {
        first = e;
      }
      if (last == null || e.compareTo(last) > 0) {
        last = e;
      }
      if (reset) {
        sourceFilteredActors.add(e.getSource());
        targetFilteredActors.add(e.getTarget());
      }
    }
    for (Actor a : sourceFilteredActors) {
      List<Event> list = playerEvents.get(a);
      if (list == null) {
        list = new ArrayList<Event>();
        playerEvents.put(a, list);
      }
      for (Event e : events) {
        if (!sourceFilteredActors.contains(e.getSource()) || !targetFilteredActors.contains(e.getTarget())) {
          continue;
        }
        if (a.equals(e.getSource())) {
          list.add(e);
        }
      }
    }
    for (Iterator<Entry<Actor, List<Event>>> iterator = playerEvents.entrySet().iterator(); iterator.hasNext();) {
      Entry<Actor, List<Event>> e = iterator.next();
      if (e.getValue().isEmpty()) {
        iterator.remove();
      }
    }
  }

  private Color nextColor() {
    Color color = TARGET_COLORS[nextColor];
    nextColor++;
    if (nextColor > TARGET_COLORS.length - 1) {
      nextColor = 0;
    }
    return color;
  }

  private void combineEvents(List<Event> addEvents) {
    if (addEvents.isEmpty() || events.isEmpty()) {
      return;
    }
    Event addFirst = addEvents.get(0);
    Event first = events.get(0);
    long timeDiff = first.getTimestamp().getTime() - addFirst.getTimestamp().getTime();
    for (Event e : addEvents) {
      e.setTimestamp(new Date(e.getTimestamp().getTime() + timeDiff));
    }
    events.addAll(addEvents);
  }

  public Mode getMode() {
    return mode;
  }

  public void setMode(Mode mode) {
    this.mode = mode;
  }

  public Set<Actor> getSourceActors() {
    return sourceActors;
  }

  public Set<Actor> getSourceFilteredActors() {
    return sourceFilteredActors;
  }

  public Set<Actor> getTargetActors() {
    return targetActors;
  }

  public Set<Actor> getTargetFilteredActors() {
    return targetFilteredActors;
  }

  public Map<Actor, List<Event>> getPlayerEvents() {
    return playerEvents;
  }

  public Event getFirst() {
    return first;
  }

  public Event getLast() {
    return last;
  }

  public List<Event> getEvents() {
    return events;
  }
}
